package org.nashua.tt151.Parsers;


/**
 * This class contains classes and enums for parsing messages transmitted from 
 * the laptop/robot. Use the static class Parsing for parsing messages.
 * A message is a string containing the command, key, value and args.
 * For more information on the structure of the message view the wiki.
 * @author Brennan Ringey
 * @version 1.2
 */
public class ProtocolParsing {
    
    private ProtocolParsing() {}
    
    /**
     * A class representing a pseudo-enum for the avaiable commands
     */
    public static class Command {
    
        public char shorthand;
    
        private Command(char c) {
            this.shorthand = c;
        }
    
        public static final Command Query = new Command('Q');
        public static final Command Reply = new Command('R');
        public static final Command Send = new Command('S');
        public static final Command Unknown = new Command('U');
        public static final Command[] ALL = { Query, Reply, Send, Unknown };

    }
    
    /**
     * A class representing a pseudo-enum for the available keys
     */
    public static class Key {
        
        public String shorthand;
        
        private Key(String c) { this.shorthand = c; }
        
        public static final Key CamAngle = new Key("CA");
        public static final Key ShooterSpeed = new Key("SS");
        public static final Key AnalogValue = new Key("AV");
        public static final Key EncoderValue = new Key("EV");
        public static final Key PWMValue = new Key("PV");
        public static final Key RelayValue = new Key("RV");
        public static final Key SolenoidValue = new Key("SV");
        public static final Key Unknown = new Key("");
        public static final Key[] ALL = { CamAngle, ShooterSpeed, AnalogValue, EncoderValue, PWMValue, RelayValue, SolenoidValue, Unknown };
        
    }
       
    
    /**
    * This class is responsible for parsing messages sent from the laptop to the
    * robot and vice versa. For more information on the protocol visit the wiki.
    * @author Brennan Ringey
    * @version 3.1
    */
    public static final class Parser {
        
        //Prevent instantiation
        private Parser() { }
        
        /**
        * Tests if the message is a Query (aka Request). A Query message
        * begins with the character Q and is not case-sensitive.
        * @param msg The message to test
        * @return True if the message is a query, false otherwise
        */
       public static boolean isQuery(String msg) {
           return (msg.charAt(0) == 'Q' || msg.charAt(0) == 'q');
       }

       /**
        * Test if the message is a Reply. A Reply message begins with the 
        * character R and is not case-sensitive.
        * @param msg The message to test
        * @return True if the message is a Reply, false otherwise
        */
       public static boolean isReply(String msg) {
           return (msg.charAt(0) == 'R' || msg.charAt(0) == 'r');
       }

       /**
        * Test if the message is a Send. A Send message begins with the
        * character S and is not case-sensitive.
        * @param msg The message to test
        * @return True if the message is a Send, false otherwise
        */
       public static boolean isSend(String msg) {
           return (msg.charAt(0) == 'S' || msg.charAt(0) == 's');
       }

       /**
        * An alternative to the above methods, this method gets the command from
        * the message header rather than testing it.
        * @param msg The message to get the command from
        * @return An enumerator from Commands, unknown is returned if no other commands match
        */
       public static Command getCommand(String msg) {
           char c = Character.toUpperCase(msg.charAt(0));
           Command command = Command.Unknown;

           for (int i = 0; i != Command.ALL.length; i++) {
               if (c == Command.ALL[i].shorthand) {
                   command = Command.ALL[i];
               }
           }
           
           return command;
       }

       /**
        * Identifies the key in the message. All of the possible keys are located on
        * the wiki and are represented by the enum Keys. If no possible key was
        * identified, Unknown is returned
        * @param msg The message containing the key to retrieve
        * @return An enumerator from Keys representing the key in the message
        */
       public static Key getKey(String msg) {
           String k = Parser.separateByColon(msg)[1];
           Key key = Key.Unknown;

           for (int i = 0; i != Key.ALL.length; i++) {
               if (k.equals(Key.ALL[i].shorthand)) {
                   key = Key.ALL[i];
               }
           }

           return key;
       }

       /**
        * Gets the value from the message. The value varies based on the key. If a 
        * value was not identified, null is returned
        * @param msg The message to get the value from
        * @return A string containing the value.
        */
       public static String getValue(String msg) {
           String[] a = separateByColon(msg);
           try {
               return a[2];
           } catch (IndexOutOfBoundsException ex) {
               return null;
           }
       }

       /**
        * Gets the additional args (if any) from the message. Arguments are 
        * separated by commas, after the final colon
        * @param msg The message containing args (if any)
        * @return An array of strings, representing the arguments
        */
       public static String[] getArgs(String msg) {
           String[] a = separateByColon(msg);
           String[] args;
           try {
               args = split(a[3], ',');
           } catch (IndexOutOfBoundsException ex) {
               args = null;
           }
           return args;
       }

       /**
        * Create a string to be transmitted over TCP. Query does not require a value
        * or arguments (Leave value null). Arguments are NOT required.
        * @param command A enumerator from Commands representing the command (Must not be Commands.Unknown)
        * @param key An enumerator from Keys representing the key (Must not be Keys.Unknown!)
        * @param value A string representing the value (Leave empty string if Query)
        * @param args A string of args to be added
        * @return A String containing the message to be sent
        */
       public static String createMessage(Command command, Key key, String value, String[] args) {
           String packet = null;

           if (command == null || key == null) { return null; }

           if (command != Command.Unknown && key != Key.Unknown) {
               packet = command.shorthand + ":" + key.shorthand + ":" + value;
               if (args.length > 0) {
                   packet += ":";
                   for (int i = 0; i != args.length; i++) {
                       packet += args[i];
                       if (i != args.length - 1) { packet += ","; }
                   }
               }
           }

           return packet;
       }

       //Helper method used by parsing methods
       private static String[] separateByColon(String msg) {
           return split(msg, ':');
       } 
       
       //Helper method used for counting the occurances of a character in a String
       private static int count(String msg, char sep) {
           int c = 0;
           for (int i=0;i<msg.length();i++) {
               if (msg.charAt(i)==sep) {
                   c++;
               }
           }
           return c;
       }
       
       //Helper method used for splitting a String based on a character
       private static String[] split(String msg, char sep) {
           String[] parts = new String[count(msg, sep)+1];
           int index = -1;
           parts[0] = msg.substring(0, msg.indexOf(sep, index+1));
           for (int i=1;i<parts.length;i++) {
               int findex = msg.indexOf(sep, index+1);
               if (findex==-1) {
                   parts[i] = msg.substring(index+1);
                   break;
               } else {
                   index=msg.indexOf(sep, findex+1);
                   if (index==-1) {
                       parts[i] = msg.substring(findex+1);
                   } else {
                       parts[i] = msg.substring(findex+1, index);
                   }
               }
           }
           return parts;
       }
    }
    
    
}
